package net.dotool;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.dotool.http.FastByteArrayOutputStream;
import net.dotool.http.StreamProgress;
import net.dotool.impl.ATxtReader;

/**
 * 文件相关的流处理
 * 
 * @author 李岩飞
 * @email eliyanfei@126.com 2017年10月27日 下午3:36:34
 * 
 */
public class IoTools {
	/** 默认缓存大小 */
	public static final int DEFAULT_BUFFER_SIZE = 1024;
	/** 默认中等缓存大小 */
	public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 4096;
	/** 默认大缓存大小 */
	public static final int DEFAULT_LARGE_BUFFER_SIZE = 8192;

	/** 数据流末尾 */
	public static final int EOF = -1;

	/**
	 * 通过文件名读取文件 jsons 包含信息如下 charset=utf-8,gbk 设置编码方式 skip=true 是否跳过首行
	 * filter=## 过滤##开始的行 lines=10 表示读取多少行数 split=分隔符
	 */
	public static List<String> readFromTxt(String file, String... jsons) {
		return readFromTxt(new File(file), jsons);
	}

	/**
	 * 统计File对象读取文件信息 jsons 包含信息如下 charset=utf-8,gbk 设置编码方式 skip=true 是否跳过首行
	 * lines=10 表示读取多少行数 split=分隔符
	 */
	public static List<String> readFromTxt(File file, String... jsons) {
		try {
			return readFromTxt(new FileInputStream(file), jsons);
		} catch (FileNotFoundException e) {
			System.out.println(file.getAbsolutePath() + ":文件不存在");
		}
		return new ArrayList<String>();
	}

	/**
	 * 读取文件信息 jsons 包含信息如下 charset=utf-8,gbk 设置编码方式 skip=true 是否跳过首行 lines=10
	 * 表示读取多少行数 split=分隔符
	 */
	public static List<String> readFromTxt(InputStream is, String... jsons) {
		final List<String> list = new ArrayList<String>();
		readTxtFile(is, new ATxtReader() {
			public void readline(String line) {
				list.add(line);
			}
		}, jsons);
		return list;
	}

	/**
	 * 通过文件名读取文件 jsons 包含信息如下 charset=utf-8,gbk 设置编码方式 skip=true 是否跳过首行 lines=10
	 * 表示读取多少行数 split=分隔符
	 */
	public static void readTxtFile(String file, ATxtReader reader, String... jsons) {
		readTxtFile(new File(file), reader, jsons);
	}

	/**
	 * 统计File对象读取文件信息 jsons 包含信息如下 charset=utf-8,gbk 设置编码方式 skip=true 是否跳过首行
	 * lines=10 表示读取多少行数 split=分隔符
	 */
	public static void readTxtFile(File file, ATxtReader reader, String... jsons) {
		try {
			readTxtFile(new FileInputStream(file), reader, jsons);
		} catch (FileNotFoundException e) {
			System.out.println(file.getAbsolutePath() + ":文件不存在");
		}
	}

	/**
	 * 读取文件信息 jsons 包含信息如下 charset=utf-8,gbk 设置编码方式 skip=true 是否跳过首行 lines=10
	 * 表示读取多少行数 split=分隔符
	 */
	public static void readTxtFile(InputStream is, ATxtReader reader, String... jsons) {
		BufferedReader br = null;
		try {
			Map<String, String> map = ConvertTools.strToMap(jsons);
			String charset = map.get("charset");
			String split = map.get("split");
			String filter = map.get("filter");
			int lines = NumberTools.toInt(map.get("lines"), -1);
			int index = NumberTools.toInt(map.get("index"), -1);
			if (charset != null)
				br = new BufferedReader(new InputStreamReader(is, charset));
			else
				br = new BufferedReader(new InputStreamReader(is));
			String str = null;
			if (map.containsKey("skip")) {
				str = br.readLine();
			}
			int n = 1;
			while ((str = br.readLine()) != null) {
				if (lines != -1 && n > lines)
					break;
				if (filter != null && str.startsWith(filter)) {
					continue;
				}
				if (split == null)
					reader.readline(str);
				else {
					if (index == -1)
						reader.readline(str.split(split));
					else
						reader.readline(str.split(split)[index]);
				}
				n++;
			}
		} catch (Exception e) {
			System.out.println("文件不存在");
		} finally {
			closeIos(is, br);
		}
	}

	/**
	 * 关闭数据流对象
	 */
	public static void closeIos(final Closeable... closeables) {
		if (null == closeables || closeables.length == 0)//
			return;
		for (final Closeable ioObject : closeables) {
			try {
				if (null != ioObject)
					ioObject.close();
			} catch (final Throwable e) {// ignore
			}
		}
	}

	public static void close(final Closeable... closeables) {
		closeIos(closeables);
	}

	/**
	 * map 的values 实现 tostring方法
	 * 
	 * @param datas
	 */
	public static void mapToFile(Map<String, Object> datas, String filePath) {
		PrintWriter pw = null;
		try {
			pw = new PrintWriter(filePath);
			for (Object obj : datas.values()) {
				pw.println(obj);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			closeIos(pw);
		}
	}

	/**
	 * map 的values 实现 tostring方法
	 * 
	 * @param datas
	 */
	public static void mapToFile(List<Map<String, ? extends Object>> list, String filePath) {
		PrintWriter pw = null;
		try {
			pw = new PrintWriter(filePath);
			for (Map<String, ? extends Object> map : list) {
				pw.println(StringTools.join(map.values(), ","));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			closeIos(pw);
		}
	}

	public static void readerFile(String filePath, String charset, ATxtReader reader) {
		readerFile(new File(filePath), charset, reader);
	}

	public static void readerFile(String filePath, ATxtReader reader) {
		readerFile(new File(filePath), null, reader);
	}

	public static void readerFile(File file, ATxtReader reader) {
		readerFile(file, null, reader);
	}

	public static void readerFile(File file, String charset, ATxtReader reader) {
		BufferedReader br = null;
		try {
			if (charset != null)
				br = new BufferedReader(new InputStreamReader(new FileInputStream(file), charset));
			else
				br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
			String str = null;
			reader.beforeRead();
			while ((str = br.readLine()) != null) {
				reader.readline(str);
			}
			reader.afterRead();
		} catch (Exception e) {
			System.out.println("文件不存在");
		} finally {
			closeIos(br);
		}
	}

	/**
	 * InputStream to String utf-8
	 *
	 * @param input
	 *            the <code>InputStream</code> to read from
	 * @return the requested String
	 * @throws NullPointerException
	 *             if the input is null
	 * @throws IOException
	 *             if an I/O error occurs
	 */
	public static String toString(InputStream input) throws IOException {
		return toString(input, Charsets.UTF_8);
	}

	/**
	 * InputStream to String
	 *
	 * @param input
	 *            the <code>InputStream</code> to read from
	 * @param charset
	 *            the <code>Charset</code>
	 * @return the requested String
	 * @throws NullPointerException
	 *             if the input is null
	 * @throws IOException
	 *             if an I/O error occurs
	 */
	public static String toString(InputStream input, Charset charset) throws IOException {
		InputStreamReader in = new InputStreamReader(input, charset);
		StringBuffer out = new StringBuffer();
		char[] c = new char[DEFAULT_BUFFER_SIZE];
		for (int n; (n = in.read(c)) != -1;) {
			out.append(new String(c, 0, n));
		}
		IoTools.closeIos(in);
		IoTools.closeIos(input);
		return out.toString();
	}

	public static String getFileNameWithoutExtendsion(final String s) {
		String ext = null;
		final int i = s.lastIndexOf('.');

		if (i > 0 && i < s.length() - 1) {
			ext = s.substring(0, i);
		}
		return ext;
	}

	/**
	 * 获得一个文件读取器
	 * 
	 * @param in
	 *            输入流
	 * @param charsetName
	 *            字符集名称
	 * @return BufferedReader对象
	 */
	public static BufferedReader getReader(InputStream in, String charsetName) {
		return getReader(in, Charset.forName(charsetName));
	}

	/**
	 * 获得一个Reader
	 * 
	 * @param in
	 *            输入流
	 * @param charset
	 *            字符集
	 * @return BufferedReader对象
	 */
	public static BufferedReader getReader(InputStream in, Charset charset) {
		if (null == in) {
			return null;
		}

		InputStreamReader reader = null;
		if (null == charset) {
			reader = new InputStreamReader(in);
		} else {
			reader = new InputStreamReader(in, charset);
		}

		return new BufferedReader(reader);
	}

	/**
	 * 从流中读取内容
	 * 
	 * @param in
	 *            输入流
	 * @param charsetName
	 *            字符集
	 * @return 内容
	 * @throws DoToolException
	 *             IO异常
	 */
	public static String read(InputStream in, String charsetName) throws DoToolException {
		FastByteArrayOutputStream out = read(in);
		return StringTools.isBlank(charsetName) ? out.toString() : out.toString(charsetName);
	}

	/**
	 * 从流中读取内容，读取完毕后并不关闭流
	 * 
	 * @param in
	 *            输入流，读取完毕后并不关闭流
	 * @param charset
	 *            字符集
	 * @return 内容
	 * @throws DoToolException
	 *             IO异常
	 */
	public static String read(InputStream in, Charset charset) throws DoToolException {
		FastByteArrayOutputStream out = read(in);
		return null == charset ? out.toString() : out.toString(charset);
	}

	/**
	 * 从流中读取内容，读到输出流中
	 * 
	 * @param in
	 *            输入流
	 * @return 输出流
	 * @throws DoToolException
	 *             IO异常
	 */
	public static FastByteArrayOutputStream read(InputStream in) throws DoToolException {
		final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
		try {
			FileTools.copyStream(in, out);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return out;
	}

	/**
	 * 从Reader中读取String，读取完毕后并不关闭Reader
	 * 
	 * @param reader
	 *            Reader
	 * @return String
	 * @throws DoToolException
	 *             IO异常
	 */
	public static String read(Reader reader) throws DoToolException {
		final StringBuilder builder = StringTools.builder();
		final CharBuffer buffer = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);
		try {
			while (-1 != reader.read(buffer)) {
				builder.append(buffer.flip().toString());
			}
		} catch (IOException e) {
			throw new DoToolException(e);
		}
		return builder.toString();
	}

	/**
	 * 从流中读取bytes
	 * 
	 * @param in
	 *            {@link InputStream}
	 * @return bytes
	 * @throws DoToolException
	 *             IO异常
	 */
	public static byte[] readBytes(InputStream in) throws DoToolException {
		final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
		try {
			FileTools.copyStream(in, out);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return out.toByteArray();
	}

	/**
	 * 读取指定长度的byte数组，不关闭流
	 * 
	 * @param in
	 *            {@link InputStream}，为null返回null
	 * @param length
	 *            长度，小于等于0返回空byte数组
	 * @return bytes
	 * @throws DoToolException
	 *             IO异常
	 */
	public static byte[] readBytes(InputStream in, int length) throws DoToolException {
		if (null == in) {
			return null;
		}
		if (length <= 0) {
			return new byte[0];
		}

		byte[] b = new byte[length];
		int readLength;
		try {
			readLength = in.read(b);
		} catch (IOException e) {
			throw new DoToolException(e);
		}
		if (readLength > 0 && readLength < length) {
			byte[] b2 = new byte[length];
			System.arraycopy(b, 0, b2, 0, readLength);
			return b2;
		} else {
			return b;
		}
	}

	/**
	 * 拷贝流 thanks to:
	 * https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java<br>
	 * 本方法不会关闭流
	 * 
	 * @param in
	 *            输入流
	 * @param out
	 *            输出流
	 * @param bufferSize
	 *            缓存大小
	 * @param streamProgress
	 *            进度条
	 * @return 传输的byte数
	 * @throws DoToolException
	 *             IO异常
	 */
	public static long copyByNIO(InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress)
			throws DoToolException {
		return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, streamProgress);
	}

	/**
	 * 拷贝文件流，使用NIO
	 * 
	 * @param in
	 *            输入
	 * @param out
	 *            输出
	 * @return 拷贝的字节数
	 * @throws DoToolException
	 *             IO异常
	 */
	public static long copy(FileInputStream in, FileOutputStream out) throws DoToolException {
		Assert.notNull(in, "FileInputStream is null!");
		Assert.notNull(out, "FileOutputStream is null!");

		final FileChannel inChannel = in.getChannel();
		final FileChannel outChannel = out.getChannel();

		try {
			return inChannel.transferTo(0, inChannel.size(), outChannel);
		} catch (IOException e) {
			throw new DoToolException(e);
		}
	}

	/**
	 * 拷贝流，使用NIO，不会关闭流
	 * 
	 * @param in
	 *            {@link ReadableByteChannel}
	 * @param out
	 *            {@link WritableByteChannel}
	 * @param bufferSize
	 *            缓冲大小，如果小于等于0，使用默认
	 * @param streamProgress
	 *            {@link StreamProgress}进度处理器
	 * @return 拷贝的字节数
	 * @throws DoToolException
	 *             IO异常
	 */
	public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize,
			StreamProgress streamProgress) throws DoToolException {
		Assert.notNull(in, "InputStream is null !");
		Assert.notNull(out, "OutputStream is null !");

		ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize <= 0 ? DEFAULT_BUFFER_SIZE : bufferSize);
		long size = 0;
		if (null != streamProgress) {
			streamProgress.start();
		}
		try {
			while (in.read(byteBuffer) != EOF) {
				byteBuffer.flip();// 写转读
				size += out.write(byteBuffer);
				byteBuffer.clear();
				if (null != streamProgress) {
					streamProgress.progress(size);
				}
			}
		} catch (IOException e) {
			throw new DoToolException(e);
		}
		if (null != streamProgress) {
			streamProgress.finish();
		}

		return size;
	}

	/**
	 * 将byte[]写到流中
	 * 
	 * @param out
	 *            输出流
	 * @param isCloseOut
	 *            写入完毕是否关闭输出流
	 * @param content
	 *            写入的内容
	 * @throws DoToolException
	 *             IO异常
	 */
	public static void write(OutputStream out, boolean isCloseOut, byte[] content) throws DoToolException {
		try {
			out.write(content);
		} catch (IOException e) {
			throw new DoToolException(e);
		} finally {
			if (isCloseOut) {
				close(out);
			}
		}
	}

	/**
	 * 将多部分内容写到流中，自动转换为UTF-8字符串
	 * 
	 * @param out
	 *            输出流
	 * @param isCloseOut
	 *            写入完毕是否关闭输出流
	 * @param contents
	 *            写入的内容，调用toString()方法，不包括不会自动换行
	 * @throws DoToolException
	 *             IO异常
	 * @since 3.1.1
	 */
	public static void writeUtf8(OutputStream out, boolean isCloseOut, Object... contents) throws DoToolException {
		write(out, Charsets.UTF_8, isCloseOut, contents);
	}

	/**
	 * 将多部分内容写到流中，自动转换为字符串
	 * 
	 * @param out
	 *            输出流
	 * @param charsetName
	 *            写出的内容的字符集
	 * @param isCloseOut
	 *            写入完毕是否关闭输出流
	 * @param contents
	 *            写入的内容，调用toString()方法，不包括不会自动换行
	 * @throws DoToolException
	 *             IO异常
	 */
	public static void write(OutputStream out, String charsetName, boolean isCloseOut, Object... contents)
			throws DoToolException {
		write(out, Charsets.charset(charsetName), isCloseOut, contents);
	}

	/**
	 * 将多部分内容写到流中，自动转换为字符串
	 * 
	 * @param out
	 *            输出流
	 * @param charset
	 *            写出的内容的字符集
	 * @param isCloseOut
	 *            写入完毕是否关闭输出流
	 * @param contents
	 *            写入的内容，调用toString()方法，不包括不会自动换行
	 * @throws DoToolException
	 *             IO异常
	 * @since 3.0.9
	 */
	public static void write(OutputStream out, Charset charset, boolean isCloseOut, Object... contents)
			throws DoToolException {
		OutputStreamWriter osw = null;
		try {
			osw = getWriter(out, charset);
			for (Object content : contents) {
				if (content != null) {
					osw.write(StringTools.trimNull(content, StringTools.EMPTY));
					osw.flush();
				}
			}
		} catch (IOException e) {
			throw new DoToolException(e);
		} finally {
			if (isCloseOut) {
				close(osw);
			}
		}
	}

	/**
	 * 将多部分内容写到流中
	 * 
	 * @param out
	 *            输出流
	 * @param isCloseOut
	 *            写入完毕是否关闭输出流
	 * @param contents
	 *            写入的内容
	 * @throws DoToolException
	 *             IO异常
	 */
	public static void writeObjects(OutputStream out, boolean isCloseOut, Serializable... contents)
			throws DoToolException {
		ObjectOutputStream osw = null;
		try {
			osw = out instanceof ObjectOutputStream ? (ObjectOutputStream) out : new ObjectOutputStream(out);
			for (Object content : contents) {
				if (content != null) {
					osw.writeObject(content);
					osw.flush();
				}
			}
		} catch (IOException e) {
			throw new DoToolException(e);
		} finally {
			if (isCloseOut) {
				close(osw);
			}
		}
	}

	/**
	 * 获得一个Writer
	 * 
	 * @param out
	 *            输入流
	 * @param charsetName
	 *            字符集
	 * @return OutputStreamWriter对象
	 */
	public static OutputStreamWriter getWriter(OutputStream out, String charsetName) {
		return getWriter(out, Charset.forName(charsetName));
	}

	/**
	 * 获得一个Writer
	 * 
	 * @param out
	 *            输入流
	 * @param charset
	 *            字符集
	 * @return OutputStreamWriter对象
	 */
	public static OutputStreamWriter getWriter(OutputStream out, Charset charset) {
		if (null == out) {
			return null;
		}

		if (null == charset) {
			return new OutputStreamWriter(out);
		} else {
			return new OutputStreamWriter(out, charset);
		}
	}
}
